Skip to main content

media_pp\core\pipeline/
builder.rs

1use std::sync::{
2    Arc, Mutex,
3    atomic::{AtomicBool, AtomicUsize},
4};
5
6use crate::{
7    bus::{Bus, BusReceiver},
8    clock::Clock,
9    control::{self, ControlReceiver, ControlSender},
10    element::{Context, SourceElement, element_pp_log, pipeline_pp_log},
11    error::Result,
12    graph::{ElementId, PipelineGraph},
13    playback_clock::PlaybackClock,
14};
15
16use super::Pipeline;
17
18/// Accumulates one or more sources into a single [`Pipeline`] — the
19/// multi-source generalization of what [`Pipeline::new`] does for exactly
20/// one. Each [`PipelineBuilder::add_source`] call gets its own background
21/// thread once [`PipelineBuilder::build`]'s [`Pipeline::run`] starts, but
22/// they all share one [`Bus`] (so [`Pipeline::bus`] sees every source's
23/// events on one channel), one [`Clock`] (so every [`crate::elements::Pacer`]
24/// anywhere in the pipeline — regardless of which source's chain it's
25/// under — agrees on the same t=0/pause timeline), and one
26/// [`PipelineGraph`] (so [`Pipeline::topology`] renders every source's
27/// own branches together).
28///
29/// [`Pipeline::new`] is exactly `PipelineBuilder::new(id).add_source(source,
30/// wire).build()` — the ergonomic single-source special case, kept as its
31/// own entry point so existing single-source callers don't need to change.
32/// Reach for `PipelineBuilder` directly once there's more than one live
33/// source to combine into one file/output — e.g. a video capture and an
34/// audio capture both feeding the same [`crate::elements::Mp4Muxer`]: two
35/// independent sources under today's [`crate::element::SourceElement`]
36/// model, but one [`Pipeline`] so `run()`/`pause()`/`resume()`/`stop()`
37/// only need to be called once, not once per source.
38pub(super) type SourceEntry = (ElementId, Box<dyn SourceElement>);
39
40pub struct PipelineBuilder {
41    id: Arc<str>,
42    bus: Bus,
43    bus_rx: BusReceiver,
44    clock: Arc<Clock>,
45    playback_clock: Arc<PlaybackClock>,
46    graph: PipelineGraph,
47    sources: Vec<SourceEntry>,
48    control_pairs: Vec<(ControlSender, ControlReceiver)>,
49}
50
51impl PipelineBuilder {
52    pub fn new(id: impl Into<String>) -> Self {
53        let id: Arc<str> = id.into().into();
54        let (bus, bus_rx) = Bus::new();
55        let clock = Arc::new(Clock::new());
56        Self {
57            id,
58            bus,
59            bus_rx,
60            playback_clock: Arc::new(PlaybackClock::new(clock.clone())),
61            clock,
62            graph: PipelineGraph::new(),
63            sources: Vec::new(),
64            control_pairs: Vec::new(),
65        }
66    }
67
68    /// Registers one more source. `wire` receives a source-scoped
69    /// [`Context`]; build detached branches with [`Context::branch`] and
70    /// commit them with [`Context::attach`]. A wiring error aborts the
71    /// builder without publishing a partially built pipeline.
72    pub fn add_source<S: SourceElement + 'static>(
73        mut self,
74        mut source: S,
75        wire: impl FnOnce(&mut S, &Arc<Context>) -> Result<()>,
76    ) -> Result<Self> {
77        *source.pp_log_mut() =
78            element_pp_log(source.element_type(), &source.name(), Some(&self.id));
79        let source_id = self.graph.add_source(source.element_type(), source.name());
80        let context = Arc::new(Context {
81            bus: self.bus.clone(),
82            pipeline_id: self.id.clone(),
83            graph: self.graph.clone(),
84            clock: self.clock.clone(),
85            playback_clock: self.playback_clock.clone(),
86            source_id,
87        });
88        wire(&mut source, &context)?;
89        self.sources.push((source_id, Box::new(source)));
90        self.control_pairs.push(control::channel());
91        Ok(self)
92    }
93
94    /// Finishes construction. At least one [`PipelineBuilder::add_source`]
95    /// call must have happened — an empty [`Pipeline`] has nothing for
96    /// [`Pipeline::run`] to ever drive, and [`Pipeline::bus`] would block
97    /// forever waiting for a source thread that will never start (nothing
98    /// left holding a [`Bus`] sender to eventually drop).
99    pub fn build(self) -> Arc<Pipeline> {
100        assert!(
101            !self.sources.is_empty(),
102            "PipelineBuilder::build called with no sources added"
103        );
104        let (control_txs, control_rxs): (Vec<_>, Vec<_>) = self.control_pairs.into_iter().unzip();
105        let pp_log = pipeline_pp_log(&self.id);
106        Arc::new(Pipeline {
107            id: self.id,
108            pp_log,
109            sources: Mutex::new(Some(self.sources)),
110            bus: Mutex::new(Some(self.bus)),
111            control_txs,
112            control_rxs: Mutex::new(Some(control_rxs)),
113            clock: self.clock,
114            playback_clock: self.playback_clock,
115            bus_rx: self.bus_rx,
116            running: Arc::new(AtomicUsize::new(0)),
117            paused: AtomicBool::new(false),
118            workers: Mutex::new(Vec::new()),
119            graph: self.graph,
120        })
121    }
122}